#include <asm/timex.h>
#include <riscv/sbi.h>
#include <riscv/csr-ops.h>

#include <linux/sched.h>
#include <linux/clockchips.h>
#include <linux/jiffies.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/percpu.h>
#include <linux/cpu.h>

#define CPUTIME_TIMER_FREQ 10000000

static volatile unsigned long tick_cycles = 0;

static unsigned int riscv_clock_event_irq = RV_IRQ_TIMER;
static DEFINE_PER_CPU(struct clock_event_device, riscv_clock_event) = {
    .features = CLOCK_EVT_FEAT_PERIODIC,
};

static irqreturn_t riscv_timer_interrupt(int irq, void *dev_id)
{
    struct clock_event_device *evdev = this_cpu_ptr(&riscv_clock_event);
    cycles_t now;

    sbi_set_timer(~0UL);

    clockevents_handle(evdev);

    now = get_cycles();
    sbi_set_timer(now + tick_cycles);

    return IRQ_HANDLED;
}

static int riscv_timer_starting_cpu(unsigned int cpu)
{
    struct clock_event_device *ce = per_cpu_ptr(&riscv_clock_event, cpu);

    clockevents_config_and_register(ce, CPUTIME_TIMER_FREQ, 100, 100000);
    csr_set(CSR_IE, 1 << RV_IRQ_TIMER);

    return 0;
}

static int riscv_timer_dying_cpu(unsigned int cpu)
{
    return 0;
}

void timer_probe(void)
{
    tick_cycles = CPUTIME_TIMER_FREQ / HZ;
    csr_clear(CSR_IE, SIP_STIP);

    sbi_set_timer(get_cycles() + tick_cycles);

    request_percpu_irq(riscv_clock_event_irq,
                       riscv_timer_interrupt,
                       "riscv-timer", &riscv_clock_event);

    cpuhp_setup_state(CPUHP_AP_CPU_LOCAL_TIMER_STARTING,
                      "clockevents/riscv/timer:starting",
                      riscv_timer_starting_cpu, riscv_timer_dying_cpu);
}
